#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace KLib.Scene
{
    public class SceneLayer : IComparable<SceneLayer>, IDisposable
    {
        #region Fields

        private SceneGraph sceneGraph;
        private List<SceneNode> nodes;
        internal List<SceneNode> nodesToBeRemoved;
        private float depth;
        private Vector2 layerOffset;

        private bool enabled = true;
        private bool visible = true;

        private bool cameraEnabled = true;
        #endregion

        #region Properties

        /// <summary>
        /// Gets the SceneGraph that is managing this Layer.
        /// </summary>
        public SceneGraph SceneGraph
        {
            get { return sceneGraph; }
            internal set { sceneGraph = value; }
        }
        /// <summary>
        /// Gets a List of the SceneNodes this layer contains.
        /// </summary>
        public List<SceneNode> Nodes
        {
            get { return nodes; }
        }

        /// <summary>
        /// Gets or Sets the Depth of this layer. (0 = Front, 1 = Back)
        /// </summary>
        public float Depth
        {
            get { return depth; }
            set { depth = value; }
        }

        /// <summary>
        /// CURRENTLY DOES NOTHING: Considering making Layers be able to have an offset from eachother. Not sure if this will make the final version
        /// </summary>
        public Vector2 LayerOffset
        {
            get { return layerOffset; }
            set { layerOffset = value; }
        }

        /// <summary>
        /// Gets or Sets if this Layer is Enabled (Meaning it will be updated by the SceneGraph)
        /// </summary>
        public bool Enabled
        {
            get { return enabled; }
            set { enabled = value; }
        }

        /// <summary>
        /// Gets or Sets if this Layer is Visible (Meaning it will be drawn by the SceneGraph)
        /// </summary>
        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        /// <summary>
        /// Gets or sets if this layer is affected by a Camera (Usefull for Background, UI, etc...)
        /// </summary>
        public bool CameraEnabled
        {
            get { return cameraEnabled; }
            set { cameraEnabled = value; }
        }
        #endregion

        #region Initialization

        /// <summary>
        /// Creates a new SceneLayer to be used by the SceneManager.
        /// </summary>
        /// <param name="depth">The depth of this layer (0 = Front, 1 = Back)</param>
        public SceneLayer(float depth)
        {
            this.nodes = new List<SceneNode>();
            this.nodesToBeRemoved = new List<SceneNode>();
            this.depth = depth;
            this.layerOffset = Vector2.Zero;
        }

        /// <summary>
        /// Creates a new SceneLayer to be used by the specified SceneManager. The layer is automatically added to the SceneGraph when using this overload.
        /// </summary>
        /// <param name="depth">The depth of this layer (0 = Front, 1 = Back)</param>
        public SceneLayer(SceneGraph sceneGraph, float depth)
        {
            this.sceneGraph = sceneGraph;
            this.sceneGraph.AddLayer(this);
            this.nodes = new List<SceneNode>();
            this.nodesToBeRemoved = new List<SceneNode>();
            this.depth = depth;
            this.layerOffset = Vector2.Zero;
        }

        #endregion

        #region Events
        public event Scene.SceneGraph.SceneNodeEventArgs NodeAdded;
        #endregion

        #region Methods

        /// <summary>
        /// Updates the Layer and all its SceneNodes
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            foreach (SceneNode node in nodes)
            {
                node.Update(gameTime);
            }

            foreach (SceneNode node in nodesToBeRemoved)
            {
                RemoveNode(node);
            }
            nodesToBeRemoved.Clear();
        }
        /// <summary>
        /// Draws the Layer and all its SceneNodes.
        /// </summary>
        /// <param name="spriteBatch">The SpriteRenderer to draw the Layer with</param>
        public void Draw(Graphics.SpriteRenderer spriteBatch)
        {
            if (spriteBatch.UseCamera != cameraEnabled)
            {
                spriteBatch.End();
                spriteBatch.UseCamera = cameraEnabled;
                spriteBatch.Begin();
            }
            foreach (SceneNode node in nodes)
            {
                node.Draw(spriteBatch);
            }
        }
        /// <summary>
        /// Adds a new SceneNode to this Layer
        /// </summary>
        /// <param name="node">The node to be added to this layer</param>
        public void AddNode(SceneNode node)
        {
            node.Layer = this;
            nodes.Add(node);

            if (NodeAdded != null)
                NodeAdded(this, node);
        }

        /// <summary>
        /// Removes a SceneNode from this layer
        /// </summary>
        /// <param name="node">The node to be removed from this layer</param>
        /// <returns>True if the node was sucessfully removed</returns>
        public bool RemoveNode(SceneNode node)
        {
            return nodes.Remove(node);
        }


        /// <summary>
        /// Removes all the SceneNodes from this layer
        /// </summary>
        public void ClearNodes()
        {
            nodes.Clear();
        }

        #region FindNode
        public SceneNode Find<T>(T objectToFind)
        {
            for (int i = 0; i < nodes.Count; i++)
            {
                SceneNode<T> tmpNode = nodes[i] as SceneNode<T>;
                if (tmpNode == null) continue;

                if (tmpNode.Object.Equals(objectToFind))
                    return nodes[i];
            }
            return null;
        }
        #endregion
        #endregion

        #region Helper Methods

        #endregion

        #region IComparable<SceneLayer> Members

        public int CompareTo(SceneLayer other)
        {
            if (this.Depth == other.Depth) return 0;
            if (this.Depth > other.Depth) return -1;
            return 1;
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion
    }
}
